Skip to content

fix(hydration): route mismatches via handleError when consumer set#14757

Open
pierluigilenoci wants to merge 2 commits into
vuejs:mainfrom
pierluigilenoci:fix/hydration-error-handler
Open

fix(hydration): route mismatches via handleError when consumer set#14757
pierluigilenoci wants to merge 2 commits into
vuejs:mainfrom
pierluigilenoci:fix/hydration-error-handler

Conversation

@pierluigilenoci

@pierluigilenoci pierluigilenoci commented Apr 24, 2026

Copy link
Copy Markdown

Summary

Hydration mismatch errors were never routed through Vue's error handling pipeline, so onErrorCaptured and app.config.errorHandler could not catch them. This made it impossible to wire hydration-mismatch reporting into observability tools (Sentry, Datadog, etc.) — see issue #13154.

This PR routes hydration mismatches through handleError, but only when an explicit consumer is present, so SSR apps that have not opted in see no behavior change.

Behavior

logMismatchError calls handleError(..., ErrorCodes.HYDRATION_MISMATCH, false) only when one of the following is true on the parent component instance:

  1. instance.appContext.config.errorHandler is set, or
  2. some ancestor component has registered onErrorCaptured.

Without a consumer, the per-mismatch warn() calls at the call sites remain the only output — matching the prior default behavior. No "Unhandled error during execution of hydration" warning is emitted for SSR apps that have not opted in.

Changes

  • packages/runtime-core/src/errorHandling.ts: adds ErrorCodes.HYDRATION_MISMATCH and its 'hydration' info label.
  • packages/runtime-core/src/hydration.ts: logMismatchError now takes the parent component instance, gates handleError on consumer presence (errorHandler or any ancestor with onErrorCaptured), and is invoked with parentComponent at every mismatch call site.
  • packages/runtime-core/__tests__/hydration.spec.ts: adds 4 tests covering: app.config.errorHandler consumer, onErrorCaptured consumer, single-emit semantics across multiple mismatches, and the no-consumer path (asserts the unhandled-error warning is not emitted).

Reviewer feedback addressed

@edison1105 raised concerns about semantics changes for apps that had already configured errorHandler/onErrorCaptured, and about the unhandled-error warning fallback for default apps. The current revision keeps default behavior unchanged and only opens the integration to apps that have explicitly opted in by registering a consumer.

Test plan

  • pnpm vitest run packages/runtime-core/__tests__/hydration.spec.ts — 108 tests pass
  • pnpm lint — clean
  • pnpm check (tsc) — clean
  • DCO sign-off on commit
  • Single commit, rebased onto current main

Closes #13154.


Status: Draft — rebased with conflict resolution. Awaiting CI verification.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Hydration mismatch errors now route through the standard error handling pipeline, invoking both app-level errorHandler and component-level onErrorCaptured callbacks when present.
    • Hydration mismatch reporting is de-duplicated, so only one error is handled even if multiple mismatches occur.
    • When no handlers are registered, mismatches still warn without emitting an “Unhandled error during execution of hydration” message.
  • Tests

    • Added coverage for hydration mismatch error handling, de-duplication, and “no handler” behavior.

@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 518375e4-d3db-40fa-ae52-5fa6b70306b9

📥 Commits

Reviewing files that changed from the base of the PR and between 1371d41 and 41dfbb2.

📒 Files selected for processing (1)
  • packages/runtime-core/src/hydration.ts

📝 Walkthrough

Walkthrough

Hydration mismatch reporting is routed through Vue's error pipeline: add ErrorCodes.HYDRATION_MISMATCH, implement logMismatchError(instance) with a dedupe guard and resetHydrationMismatchState(), update mismatch call sites to pass component context, and add tests verifying handlers receive the error.

Changes

Hydration mismatch handling

Layer / File(s) Summary
Error code mapping
packages/runtime-core/src/errorHandling.ts
Added HYDRATION_MISMATCH to ErrorCodes and mapped it to 'hydration' in ErrorTypeStrings.
Hydration core behavior
packages/runtime-core/src/hydration.ts
Refactored logMismatchError(instance) guarded by hasLoggedMismatchError, added hasErrorCaptured(instance) helper, imported handleError/ErrorCodes, routed via handleError(..., ErrorCodes.HYDRATION_MISMATCH), and exported resetHydrationMismatchState().
Call-site updates
packages/runtime-core/src/hydration.ts
Updated seven hydration mismatch call sites to invoke logMismatchError(parentComponent) across text mismatches, children mismatches, content mismatches, property mismatches, and fragment failures.
Test coverage
packages/runtime-core/__tests__/hydration.spec.ts
Imported onErrorCaptured and resetHydrationMismatchState; added test suite verifying app and component error handlers receive the hydration mismatch Error, that it is emitted once despite multiple mismatches, and that warnings behave correctly without registered handlers.

Sequence Diagram

sequenceDiagram
  participant HydrationSystem
  participant logMismatchError
  participant handleError
  participant AppErrorHandler
  participant ComponentCapture

  HydrationSystem->>logMismatchError: detect mismatch (parentComponent)
  logMismatchError->>logMismatchError: check hasLoggedMismatchError
  alt not logged
    logMismatchError->>logMismatchError: set hasLoggedMismatchError = true
    logMismatchError->>handleError: handleError(Error("Hydration completed but contains mismatches."), instance, ErrorCodes.HYDRATION_MISMATCH)
    handleError->>AppErrorHandler: invoke if configured
    handleError->>ComponentCapture: deliver to captured ancestor handlers (onErrorCaptured)
  else already logged
    logMismatchError->>logMismatchError: skip duplicate emission
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • vuejs/core#14857: Also modifies packages/runtime-core/src/hydration.ts to deduplicate and guard hydration-mismatch logging and warnings.
  • vuejs/core#14833: Adjusts hydration-mismatch error reporting by changing when and how logMismatchError is triggered during hydration.

Suggested labels

ready to merge, :hammer: p3-minor-bug

Suggested reviewers

  • Doctor-wu
  • johnsoncodehk

🐰 I hopped through code at break of dawn,
Brought mismatches into handlers spawned.
With a guard and reset, we log just once,
Errors now bubble to the app and the branch.
Hop on, devs — catch them at a glance. 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(hydration): route mismatches via handleError when consumer set' accurately and concisely describes the main change—routing hydration mismatch errors through Vue's error handling pipeline when an error consumer is present.
Linked Issues check ✅ Passed The PR fully implements all objectives from #13154: hydration mismatch errors now route through Vue's error pipeline (onErrorCaptured and app.config.errorHandler), providing richer Error context for observability tool integration while preserving default behavior via consumer-gating.
Out of Scope Changes check ✅ Passed All changes are scoped to addressing #13154: error code addition, hydration error routing logic, test coverage for the four consumer scenarios, and no unrelated alterations are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/hydration.ts`:
- Around line 68-92: The current call to handleError(...) always routes through
logError() and emits extra dev warnings; change the logic so handleError is only
invoked when there is an actual consumer (check
instance.appContext.config.errorHandler or hasErrorCaptured(instance)), and when
no consumer exists fall back to directly calling console.error(new
Error('Hydration completed but contains mismatches.')) instead of handleError;
update both the __TEST__ branch and the default branch to use this gating around
handleError and preserve the existing ErrorCodes.HYDRATION_MISMATCH constant
when routing to handleError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4511270-f4c7-4adc-aa7e-d07fa84ea855

📥 Commits

Reviewing files that changed from the base of the PR and between 3310eea and 5e1c9ff.

📒 Files selected for processing (3)
  • packages/runtime-core/__tests__/hydration.spec.ts
  • packages/runtime-core/src/errorHandling.ts
  • packages/runtime-core/src/hydration.ts

Comment thread packages/runtime-core/src/hydration.ts Outdated
@pierluigilenoci

Copy link
Copy Markdown
Author

Hi — friendly ping. Is this PR still on the radar for review? Happy to rebase or make changes if needed. Thanks!

@edison1105

Copy link
Copy Markdown
Member

I agree the observability use case is valid, but I don't think this should be merged in the current form.

Routing hydration mismatch reporting through handleError() changes the existing semantics: onErrorCaptured can now stop propagation, existing app.config.errorHandler hooks will start receiving hydration mismatches, and the fallback console behavior changes when there is no explicit consumer. So this is more of a minor feature than a bug fix.

I think the safer direction is to preserve the current default behavior and only expose hydration mismatch reporting to user handlers when there is an explicit consumer, or consider an app-level / warning-handler based API instead of putting it fully into the component error boundary pipeline.

@pierluigilenoci

Copy link
Copy Markdown
Author

Hi — friendly follow-up. CI is green and all checks pass. Would you be able to review when you get a chance? Thank you!

@pierluigilenoci pierluigilenoci force-pushed the fix/hydration-error-handler branch from 5e1c9ff to 81d3a55 Compare May 22, 2026 09:14
@pierluigilenoci

Copy link
Copy Markdown
Author

@edison1105 thanks for the careful review — fully agree. I just pushed a rework that addresses your concerns:

handleError is now gated on an explicit consumer. logMismatchError only routes through handleError(..., HYDRATION_MISMATCH, false) when one of the following is true on the parent component instance:

  1. appContext.config.errorHandler is set, or
  2. some ancestor component has registered onErrorCaptured.

Without a consumer, the per-mismatch warn() calls at the call sites remain the only output — which is exactly the prior default behavior. SSR apps that have not opted in see no "Unhandled error during execution of hydration" warning and no behavior change at all. Apps that have opted in (errorHandler / onErrorCaptured) get the integration with the error pipeline that issue #13154 asked for.

Other concerns:

  • onErrorCaptured stopping propagation: only relevant when an ancestor has opted in by registering it. The default-no-consumer path is unchanged.
  • Existing app.config.errorHandler hooks suddenly receiving hydration mismatches: fair point as a semantics change, but it requires the user to have explicitly set errorHandler and be doing SSR with mismatches; in that narrow case the new signal is what observability users ("Hydration completed but contains mismatches" error not caught by the Vue error handler #13154) want. Happy to add a release-note entry if you'd like.

Tests cover all four cases: errorHandler consumer, onErrorCaptured consumer, single-emit guard, and the no-consumer path (asserts "Unhandled error during execution of hydration" is not warned).

Let me know if the gating-on-consumer shape works for you, or if you'd prefer the app-level / warning-handler API instead — happy to iterate.

@pierluigilenoci pierluigilenoci changed the title fix(hydration): route hydration mismatch errors through handleError (fix #13154) fix(hydration): route hydration mismatch errors through handleError when a consumer is set (fix #13154) May 22, 2026
@pierluigilenoci pierluigilenoci changed the title fix(hydration): route hydration mismatch errors through handleError when a consumer is set (fix #13154) fix(hydration): route mismatches via handleError when consumer set May 22, 2026
@pierluigilenoci pierluigilenoci force-pushed the fix/hydration-error-handler branch from 81d3a55 to 68656d1 Compare May 22, 2026 09:26
Hydration mismatch errors were never routed through Vue's error
handling pipeline, so onErrorCaptured and app.config.errorHandler
could not catch them — making it hard to wire hydration mismatch
reporting into observability tools (fix vuejs#13154).

logMismatchError now calls handleError(..., HYDRATION_MISMATCH, false)
only when an explicit consumer is present:

  - app.config.errorHandler is set, or
  - some ancestor has registered onErrorCaptured.

Without a consumer, the per-mismatch warn() calls at the call sites
remain the only output, matching the prior default behavior — no
'Unhandled error during execution of hydration' warning is emitted
for SSR apps that have not opted in.

Adds ErrorCodes.HYDRATION_MISMATCH plus its 'hydration' info label,
passes the parent component instance to logMismatchError() at every
call site, and adds tests covering: app.config.errorHandler consumer,
onErrorCaptured consumer, single-emit semantics across multiple
mismatches, and the no-consumer path.

Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
@pierluigilenoci pierluigilenoci force-pushed the fix/hydration-error-handler branch from 68656d1 to 1371d41 Compare June 1, 2026 15:13
@claudiaballano

Copy link
Copy Markdown

@pierluigilenoci @edison1105 Thanks for working on this. Any updates on your side that could help unblock it? Happy to help if needed.

Keep the original production behavior unchanged: apps without an
explicit error consumer (errorHandler / onErrorCaptured) still see
the console.error('Hydration completed but contains mismatches.')
output. Only route through handleError when a consumer is present.

This addresses reviewer feedback that the previous revision removed
the default fallback, changing semantics for apps that had not opted in.

Signed-off-by: Pierluigi Lenoci <pierluigi.lenoci@gmail.com>
@pierluigilenoci

Copy link
Copy Markdown
Author

@edison1105 thank you for the feedback — you're right that the previous revision dropped the production console.error fallback for apps without an explicit consumer.

I just pushed a fix that preserves the original default behavior exactly:

  • With consumer (app.config.errorHandler or ancestor onErrorCaptured): routes through handleError(err, instance, ErrorCodes.HYDRATION_MISMATCH, false) — this is the new opt-in integration for observability tools.
  • Without consumer: emits the same console.error('Hydration completed but contains mismatches.') as before — no behavior change for existing SSR apps.

The change is minimal: a single hasConsumer guard with an else branch that calls console.error(err.message) (matching the prior output). All 110 hydration tests pass, lint and type check are clean.

Let me know if this shape works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Hydration completed but contains mismatches" error not caught by the Vue error handler

3 participants